import socket
import threading
import time
import struct
import redis
import queue
import binascii
class TPgk (object):
    def __init__(self):
        self.controlBytes = None
        self.bodyBytes = None

        self.isIframe = False
        self.isInitSuccess = False
        self.isTest = False
    @staticmethod
    def getActiveBytes():
        bts = bytes([0x68 , 0x04 ,0x07 , 0x00 , 0x00 , 0x00])
        return  bts
    @staticmethod
    def getCallAllBytes(sendNo , recNo):
        sendNoBytes = sendNo.to_bytes(2 , byteorder='little')
        recNoBytes = recNo.to_bytes(2 , byteorder='little')
        bts = bytes([0x68 , 0x0e ,
            sendNoBytes[0] , sendNoBytes[1], recNoBytes[0] , recNoBytes[1] ,
            0x64 , 0x01 , 0x06 , 0x00 , 0x01 , 0x00 ,
            0x00 , 0x00 , 0x00 , 0x14
        ])
        return  bts

    def saveBytes(self ):
        try:
            time.sleep(0.001)
            t = str(time.time()).replace(".",'_')+".txt"
            fn = "d:/Project/ic104/logs/"+t
            c = ""
            for i in range(len(self.bodyBytes)):
                b = self.bodyBytes[i]
                c=c+' '+str(b)
            with open( fn , 'w' , encoding='utf-8') as f:
                f.write(c)

        except Exception as e:
            print(e)
class TPgkObj(object):
    def __init__(self):
        self.addr =0
        self.val =0
        self.desc=''
class TPgkInfo(object):
    def __init__(self):
        self.flag = False
        self.typ = 0x00
        self.vsq = 0x00
        self.objAddrContinue = False
        self.objCount =0
        self.cot = 0x0000
        self.adr = 0x0000
        self.objs =[]
    def assign(self , bodyBytes):
        try:
            self.typ = bodyBytes[0]
            self.vsq = bodyBytes[1]
            self.objAddrContinue = self.vsq & 0x80 == 0x80
            self.objCount = self.vsq & 0x07f
            self.cot = int.from_bytes(bytes([bodyBytes[2] , bodyBytes[3]]) , byteorder='little');
            self.adr = int.from_bytes(bytes([bodyBytes[4] , bodyBytes[5]]) , byteorder='little');
            if self.objCount>0:
                if( self.typ==0x01  or self.typ==0x03):
                    self.readObjsM_SP_NA_1(bodyBytes)
                if( self.typ==0x0d ):
                    self.readObjsM_ME_NC_1(bodyBytes)
            self.flag = True

        except Exception as e :
            print('assign error');
            print(e);

    def readObjAddrValue(self , bodyBytes , objIndex ,   dataType , dataLen ,  qfLen , objTimeLen):
        addr = 0
        val =0
        try:
            if self.objAddrContinue :
                offset = 6
                addr = int.from_bytes(bytes([bodyBytes[offset],bodyBytes[offset+1]]),byteorder='little') + objIndex
                offset = offset +3
                pos = objIndex * ( dataLen + qfLen + objTimeLen) + offset
                dataBytes = bodyBytes[pos:pos+dataLen]
                print(('index='+str(objIndex) +'\t'+'pos='+str(pos)))

                if dataType=='byte':
                    val = bodyBytes[pos]
                elif dataType =='int':
                    val = int.from_bytes(dataBytes , byteorder='little')
                elif dataType == 'float':
                    val = struct.unpack("<f",dataBytes)[0]
            else:
                offset = 6 + objIndex * (dataLen + qfLen + objTimeLen +3)
                addr = int.from_bytes(bytes([bodyBytes[offset],bodyBytes[offset+1]]),byteorder='little')
                pos = offset + 3
                dataBytes = bodyBytes[pos:pos+dataLen]
                if dataType=='byte':
                    val = bodyBytes[pos]
                elif dataType =='int':
                    val = int.from_bytes(dataBytes , byteorder='little')
                elif dataType == 'float':
                    val = struct.unpack("<f",dataBytes)[0]
            addr = addr = 0x4000

        except Exception as e:
            print(e)
        return  ( addr , val)

    #01,03
    def readObjsM_SP_NA_1(self , bodyBytes):
        offset = 6
        adr = int.from_bytes(bytes([bodyBytes[offset],bodyBytes[offset+1]]),byteorder='little')
        offset = offset + 2
        offset = offset + 1
        for i in range(self.objCount):
            if i < len(bodyBytes):
                obj = TPgkObj()
                obj.val = bodyBytes[offset+i]
                obj.addr = adr + i
                self.objs.append(obj)
                #print( 'add:'+str(obj.addr) +'\ttyp='+str(self.typ)+'\tval='+str(obj.val))
            else:
                print('-- readObjsM_SP_NA_1 obj assign error ---')

    #0D
    def readObjsM_ME_NC_1(self , bodyBytes):
        dataType = 'float'
        dataLen=4
        qfLen=1
        objTimeLen=0
        for i in range(self.objCount):
            addr , val = self.readObjAddrValue(bodyBytes , i , dataType , dataLen , qfLen , objTimeLen)
            obj = TPgkObj()
            obj.val = val
            obj.addr = addr
            self.objs.append(obj)


        '''
        offset = 6
        adr = int.from_bytes(bytes([bodyBytes[offset],bodyBytes[offset+1]]),byteorder='little')
        adr = adr - 0x4000
        offset = offset + 2
        offset = offset + 1
        for i in range(self.objCount):
            if i+offset < len(bodyBytes):
                obj = TPgkObj()
                pos = i*5 + offset
                ba = bytearray(bodyBytes[pos:pos+4])
                obj.val = struct.unpack("<f",ba)[0]
                print('-------------  offset ----------')
                print('i='+str(i)+"/"+str(self.objCount)+"-s:"+str(len(bodyBytes)) +"\toffset="+str(offset)+'\tpos='+str(pos)+'\tval='+str(obj.val))
                s=""
                for k in range(len(ba)):
                    s=s+str(ba[k])+"\t"
                print(s)
                obj.addr = adr + i
                self.objs.append(obj)
                #print( 'add:'+str(obj.addr) +'\ttyp='+str(self.typ)+'\tval='+str(obj.val))
            else:
                print('-- readObjsM_ME_NC_1 obj assign error ---')
        '''
class TRedis(object):
    def __init__(self):
        self.host='182.106.182.20'
        self.port=10090
        self.db = 2

        self.pool = None
        self.r = None

        self.buf = queue.Queue()
    def writeDict(self, path , data):
        print('write dict '+ path +'\t'+ str(data['v']) +'\t'+ str(data['t']))
        for item in data:
            self.r.hset(path, item, data[item])
    def push(self, path , data):
        self.buf.put({
            "key":path ,
            "data":data
        })
    def exeFun(self):
        self.pool = redis.ConnectionPool(host=self.host, port=self.port, decode_responses=True)
        self.r = redis.Redis(connection_pool=self.pool)
        while 1>0:
            lst=[]
            while not self.buf.empty() :
                lst.append(self.buf.get())
            if len(lst)>0:
                for item in lst:
                    self.writeDict(item["key"] , item["data"])
            time.sleep(0.1)
    def start(self):
        t = threading.Thread(target=self.exeFun , name='redis' , daemon=True)
        t.start()


class TMaster(object):
    def __init__(self):
        self.version = 'ice2002-master'
        self.host = "10.168.1.100"
        self.port = 2404
        self.redisHost='182.106.182.20'
        self.redisPort=10090
        self.redisDb = 2

        self.sNo=0
        self.rNo=0
        self.lastIFrame = None
        self.mainvThread = None
        self.isConnected = False

        self.lastTm = 0
        self.lastCallFullTm = 0

        #self.redisClient =  TRedis()
        self.pgkBuf = queue.Queue()
    def connectSlave(self):
        try:
            self.skt =socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
            self.skt.connect((self.host, self.port))
            self.isConnected = True
        except Exception as e:
            print('------------- connecSlave error --------')
            print(e)
    def active(self):
        try:
            pgkBytes = bytes([0x68 , 0x04 , 0x07 , 0x00 , 0x00 , 0x00 ])
            self.sktsend(pgkBytes)
        except Exception as e:
            print('------------- active error --------')
            print(e)
    def callFullData(self, sNo , rNo):
        try:
            sendNoBytes = (sNo).to_bytes(2 , byteorder='little')
            recNoBytes = (rNo).to_bytes(2 , byteorder='little')
            c0= sendNoBytes[0]
            c1 = sendNoBytes[1]
            c2 = recNoBytes[0]
            c3 = recNoBytes[1]
            pgkBytes = bytes([0x68 , 0x0e , c0 , c1 , c2 , c3 ,
                              0x64, 0x01, 0x06, 0x00, 0x01, 0x00,
                              0x00, 0x00, 0x00, 0x14,
                              ])
            self.sktsend(pgkBytes)
            self.lastCallFullTm = time.time()
        except Exception as e:
            print('------------- callFullData error --------')
            print(e)
    def responseSFrame(self):
        try:
            respNoBytes = (self.rNo+2).to_bytes(2 , byteorder='little')
            c0= respNoBytes[0]
            c1 = respNoBytes[1]
            pgkBytes = bytes([0x68 , 0x04 , 0x01 , 0x00 , c0 , c1])
            self.sktsend(pgkBytes)
        except Exception as e:
            print('------------- responseSFrame error --------')
            print(e)
    def pingSlave(self):
        try:
            pgkBytes = bytes([0x68 , 0x04 , 0x43 , 0x00 , 0x00 , 0x00  ])
            self.sktsend(pgkBytes)
            self.lastTm = time.time()
        except Exception as e:
            print('------------- pingSlave error --------')
            print(e)
    def pongSlave(self):
        try:
            pgkBytes = bytes([0x68 , 0x04 , 0x83 , 0x00 , 0x00 , 0x00  ])
            self.sktsend(pgkBytes)
            self.lastTm = time.time()
        except Exception as e:
            print('------------- pongSlave error --------')
            print(e)
    def recvPgk(self):
        pgk = TPgk()
        try:
            hb = self.skt.recv(1)
            pgk.flag = hb!=None and len(hb)==1 and hb[0]==0x68
            if( pgk.flag):
                size = self.skt.recv(1)[0]
                pgk.controlBytes = self.skt.recv(4)
                if( size>4):
                    pgk.bodyBytes = self.skt.recv(size -4)
                    pgk.saveBytes()
                    pgk.isIframe = pgk.controlBytes[0] % 2 ==0  and pgk.controlBytes[2] % 2 ==0
                    if pgk.isIframe :
                        pgk.isInitSuccess = pgk.bodyBytes[0] == 0x46
                        pgkInfo = TPgkInfo()
                        pgkInfo.assign(pgk.bodyBytes)
                        print(pgk.bodyBytes)
                        if len(pgkInfo.objs)>0:
                            self.pgkBuf.put(pgkInfo)
                            '''
                            nowTm = time.time()
                            for i in range(len(pgkInfo.objs)):
                                obj = pgkInfo.objs[i]
                                key = "/ice104/"+str(pgkInfo.adr)+"/"+str(pgkInfo.typ)+"/"+str(obj.addr)
                                data ={
                                    "v":obj.val ,
                                    "t":nowTm
                                }
                                self.redisClient.push(key ,data)
                            '''
                pgk.isTest = pgk.controlBytes[0] == 0x43
        except Exception as e:
            print('------------- recvPgk error --------')
            print(e)
        if pgk.flag:
            c = ''
            for i in range(len(pgk.controlBytes)):
                c = c+' '+str(pgk.controlBytes[i])
            if pgk.bodyBytes!=None:
                for i in range(len(pgk.bodyBytes)):
                    c = c+' '+str(pgk.bodyBytes[i])
        return pgk
    def open(self):
        try:
            self.connectSlave()
            self.active()
            nowTm = time.time()
            self.lastTm = nowTm
            self.lastCallFullTm = nowTm
            #self.redisClient.start()

            saveThread = threading.Thread(target=self.savePgk , name='saveThread', daemon=True)
            saveThread.start()
            monitorThread = threading.Thread(target=self.monitor , name='monitorThread', daemon=True)
            monitorThread.start()
            while 1>0 :
                pgk = self.recvPgk()
                if pgk.flag :
                    if pgk.isIframe :
                        self.sNo = int.from_bytes(bytes([pgk.controlBytes[2] , pgk.controlBytes[3]]) , byteorder='little')
                        self.rNo = int.from_bytes(bytes([pgk.controlBytes[0] , pgk.controlBytes[1]]) , byteorder='little')
                        self.responseSFrame()
                        if pgk.isInitSuccess:
                            self.callFullData(self.sNo , self.rNo)
                    if pgk.isTest:
                        self.pongSlave()
                else:
                    print('slave is offline.............')
                    break
            self.isConnected = False
        except Exception as e:
            print('------------- open error --------')
            print(e)

    def savePgk(self):
        try:
            pool = redis.ConnectionPool(host=self.redisHost, port=self.redisPort,  decode_responses=True)

            r = redis.Redis(connection_pool= pool)
            r.select(2)
            while 1>0 :
                time.sleep(0.1)
                try:
                    if not self.pgkBuf.empty():
                        pgkInfo = self.pgkBuf.get()
                        key = "/ice104/"+str(pgkInfo.adr)+"/"+str(pgkInfo.typ)
                        datas ={}
                        for obj in pgkInfo.objs:
                            datas[str(obj.addr)] = obj.val
                        r.hmset(key , datas)
                except Exception as ee:
                    print(ee)

        except Exception as e:
            print('------------- savePgk error --------')
            print(e)
    def monitor(self):
        try:
            while self.isConnected:
                nowTm = time.time()
                dltTest = nowTm - self.lastTm
                dltCallFull = nowTm - self.lastCallFullTm
                if dltTest > 12:
                    self.pingSlave()
                if dltCallFull > 900:
                    print('-----  call full ----')
                    sNo = self.sNo
                    rNo = self.rNo +2
                    self.callFullData(sNo , rNo)


        except Exception as e:
            print('------------- open error --------')
            print(e)
    def start(self):
        try:
            self.mainvThread = threading.Thread( target=self.open , name="mainThread" , daemon=True)
            self.mainvThread.start()
        except Exception as e:
            print('------------- start error --------')
            print(e)
    def log(self , flag , bytes):
        try:
            c = '-------------- ' + flag +' ' +  time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) +' --------------'
            print(c)
            c =""
            for i in range(len(bytes)):
                c = c +' '+ str(bytes[i])
            print(c)
        except Exception as e:
            print('------------- pongSlave error --------')
            print(e)
    def sktsend(self ,  bytes):
        try:
            self.skt.send(bytes)
            #self.log('send' , bytes)
        except Exception as e:
            print('------------- pongSlave error --------')
            print(e)